package qc.common.core.utils;

import org.apache.commons.lang3.StringUtils;
import qc.common.core.enums.qms.QmsPointTimeSegmentEnum;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

/**
 * QMS时间工具类，提供时间规整、求总天数等方法
 * 时间规整：用于根据不同的时段类型对传入的时间规整到一个时间段的代表时间点上（默认均使用时间段的开始时间点代表该时间段）
 * 求总天数：求指定时段类型的时标表示时段跨度天数
 *
 * @author QuCheng Tech
 * @since 2025/9/10
 */
public class QmsTimeUtil {

    /**
     * 将时间规整到指定的间隔秒数对应的时间，时间规整到更靠近间隔的向前或前后的时间点
     * 如果指定的规整秒数为空或负值表示不规整，指定的秒数为0或1表示规整到1秒，指定的秒数大于1小时3600秒时自动取余数
     *
     * @param time          时间
     * @param regularSecond 规整秒数
     * @return java.util.Date
     * @author QuCheng Tech
     * @since 2025/9/10
     */
    public static LocalDateTime getRegularTime(LocalDateTime time, Integer regularSecond) {
        if (time != null) {
            //为避免返回的对象直接使用导致引用地址相同，在此重新创建一个返回对象
            LocalDateTime result = LocalDateTime.of(time.toLocalDate(), time.toLocalTime());

            if (regularSecond != null && regularSecond.intValue() >= 0x0) {
                int regSecond = regularSecond.intValue();
                //如果传入的规整秒数大于1小时 3600秒，判定为设置错误，自动取除以3600后的余数
                if (regSecond >= 3600)
                    regSecond = regSecond % 3600;
                //如果规整秒数为空、负值均表示不规整，直接返回即可
                if (regSecond >= 0x0) {
                    //设置毫秒数为0
                    result = time.withNano(0x0);

                    //如果规整秒数为0或者1，均表示规整到1秒（不需要进行判断处理，直接返回）；如果规整秒数＞1，表示规整到指定间隔的秒数
                    if (regSecond > 0x1) {
                        //取传入时间距离整点0分过去的秒数（规整的时间点以0分0秒作为开始）
                        int seconds = time.getMinute() * 60 + time.getSecond();
                        //先设置时间的秒数为0、分钟数为0
                        result = result.withSecond(0x0);
                        result = result.withMinute(0x0);

                        //取传入时间的秒数与规整秒数的整除数和余数
                        int interval = seconds / regSecond;//整除得到商
                        int remain = seconds % regSecond;//余数

                        //计算出规整到的秒数
                        int calcSecond = 0x0;
                        if (interval > 0x0)
                            calcSecond += interval * regSecond;
                        if (remain > 0x0) {
                            //余数大于0时需要判断时靠近前一个时间点还是后一个时间点
                            //判断方法：如果余数*2 ＞ 规整秒数则靠近后一个时间点，秒数再加一次规整秒数；否则不需要再加秒数
                            if (remain * 2 > regSecond)
                                calcSecond += regSecond;
                        }

                        //设置时间的秒数，在0分0秒基础上加上总秒数
                        result = result.plusSeconds(calcSecond);
                    }
                }
            }

            return result;
        }

        return null;
    }

    /**
     * 根据时段类型计算时间点所在时段的代表时间，返回的代表时间根据时段类型保留时间精度
     * 作用等同于：获取时段起始时间点、获取按时段类型进行时间精度保留时间
     * 时段代表时间：均使用时段的开始时间点代表时段时间
     * 保留时间精度:分钟精确到分、小时精确到时、天精确到天、以此类推
     *
     * @param time  时间点
     * @param tmseg 时段类型
     * @return java.util.Date
     * @author QuCheng Tech
     * @since 2025/9/10
     */
    public static LocalDateTime getPeriodTime(LocalDateTime time, QmsPointTimeSegmentEnum tmseg) {
        if (time != null) {
            //先将时间保留到秒，去除毫秒数
            LocalDateTime result = time.withNano(0x0);
            //如果时段类型是未定义或实时不做精度保留处理
            if (tmseg == QmsPointTimeSegmentEnum.UN_DEFINE || tmseg == QmsPointTimeSegmentEnum.REAL)
                return result;

            //获取传入时间的月份、日期、小时、分钟和秒，方便后面使用
            int month = time.getMonthValue();
            int day = time.getDayOfMonth();
            int hour = time.getHour();
            int minute = time.getMinute();
            int second = time.getSecond();

            //先初始化返回时间为传入时间的所在年的1月1日
            result = result.withSecond(0x0).withMinute(0x0).withHour(0x0).withDayOfMonth(0x1).withMonth(0x1);

            //如果时段类型是年，返回传入时间所在年的1月1日
            if (tmseg == QmsPointTimeSegmentEnum.YEAR)
                return result;

            //如果时段类型不是年，先设置月份为传入时间的月份
            result = result.withMonth(month);

            //如果时段类型是月，在此返回已赋值结果
            if (tmseg == QmsPointTimeSegmentEnum.MONTH)
                return result;

            //如果传入时间是季度，根据月份进行判断处理后返回
            if (tmseg == QmsPointTimeSegmentEnum.QUARTER) {
                //月份数值为1-12，季度的时间使用1、4、7、10月表示
                if (month >= 10)
                    result = result.withMonth(10);
                else if (month >= 0x7)
                    result = result.withMonth(0x7);
                else if (month >= 0x4)
                    result = result.withMonth(0x4);
                else
                    result = result.withMonth(0x1);

                return result;
            }

            if (tmseg == QmsPointTimeSegmentEnum.TEN_DAYS) {
                //天数值使用1-31，旬的时间使用1、11、21表示上、中、下旬
                if (day >= 21)
                    result = result.withDayOfMonth(21);
                else if (day >= 11)
                    result = result.withDayOfMonth(11);
                else
                    result = result.withDayOfMonth(1);

                return result;
            }

            if (tmseg == QmsPointTimeSegmentEnum.WEEK) {
                //如果时段类型是周，暂时未实现
                return result;
            }

            //设置日为传入时间的日期
            result = result.withDayOfMonth(day);

            //如果时段类型是日，在此返回已赋值结果
            if (tmseg == QmsPointTimeSegmentEnum.DAY)
                return result;

            //设置小时为传入时间的小时数
            result = result.withHour(hour);

            //如果时段类型是小时，在此返回已赋值结果
            if (tmseg == QmsPointTimeSegmentEnum.HOUR)
                return result;

            //设置分钟为传入时间的分钟数
            result = result.withMinute(minute);

            //如果时段类型是分钟，在此返回已赋值结果
            if (tmseg == QmsPointTimeSegmentEnum.MINUTE)
                return result;

            //设置秒为传入时间的秒数
            result = result.withSecond(second);

            //如果时段类型是秒，在此返回已赋值结果
            if (tmseg == QmsPointTimeSegmentEnum.REAL)
                return result;

            //其他情况，默认返回精确到秒的时间
            return result;
        }

        return null;
    }

    /**
     * 获取下一个时段的起始时间，如果时间点就是起始时间返回值是下一个时段的时间点；如果时段类型是小时，时段长度默认为60分钟；
     * 作用等同于：获取时段截止时间点
     *
     * @param time  时间点
     * @param tmseg 时段类型
     * @return java.util.Date
     * @author QuCheng Tech
     * @since 2025/9/13
     */
    public static LocalDateTime getNextPeriodTime(LocalDateTime time, QmsPointTimeSegmentEnum tmseg) {
        return getNextPeriodTime(time, tmseg, 0x0);
    }

    /**
     * 获取下一个时段的起始时间，如果时间点就是起始时间返回值是下一个时段的时间点；如果时段类型是小时，时段长度默认为60分钟；
     * 作用等同于：获取时间截止时间点
     *
     * @param time            时间点
     * @param tmseg           时段类型
     * @param intervalMinutes 时段长分钟数，时段类型为分钟和小时时有效，指定无效时使用默认值
     * @return java.util.Date
     * @author QuCheng Tech
     * @since 2025/9/13
     */
    public static LocalDateTime getNextPeriodTime(LocalDateTime time, QmsPointTimeSegmentEnum tmseg, int intervalMinutes) {
        return getIntervalPeriodTime(time, tmseg, 0x1, 0x0);
    }

    /**
     * 获取指定间隔段次数量的时段起始时间；间隔段次数量可正可负；如果时段类型是小时，时段长度默认为60分钟；
     *
     * @param time     时间点
     * @param tmseg    时段类型
     * @param interval 间隔段次数量，为负表示时间往前，为正表示时间往后
     * @return java.time.LocalDateTime
     * @author QuCheng Tech
     * @since 2025/9/23
     */
    public static LocalDateTime getIntervalPeriodTime(LocalDateTime time, QmsPointTimeSegmentEnum tmseg, int interval) {
        return getIntervalPeriodTime(time, tmseg, interval, 0x0);
    }

    /**
     * 获取指定间隔段次数量的时段起始时间；间隔段次数量可正可负；
     *
     * @param time            时间点
     * @param tmseg           时段类型
     * @param interval        间隔段次数量，为负表示时间往前，为正表示时间往后
     * @param intervalMinutes 时段长分钟数，时段类型为分钟和小时时有效，指定无效时使用默认值
     * @return java.time.LocalDateTime
     * @author QuCheng Tech
     * @since 2025/9/23
     */
    public static LocalDateTime getIntervalPeriodTime(LocalDateTime time, QmsPointTimeSegmentEnum tmseg, int interval, int intervalMinutes) {
        if (time != null) {
            //为避免返回的对象直接使用导致引用地址相同，在此重新创建一个返回对象
            LocalDateTime result = LocalDateTime.of(time.toLocalDate(), time.toLocalTime());
            //时段类型是未定义、实时的无法确定时段长度，返回传入的时间点
            if (tmseg == QmsPointTimeSegmentEnum.UN_DEFINE || tmseg == QmsPointTimeSegmentEnum.REAL)
                return result;
            //如果传入的间隔段次数为0，返回传入的时间
            if (interval == 0x0)
                return result;
            //根据输入的段次数是正是负判断是加还是减
            boolean isplus = interval > 0x0;
            int count = Math.abs(interval);

            //先获取传入时间点所在的时段起始时间作为返回时间
            LocalDateTime beginPeriodTime = getPeriodTime(time, tmseg);

            //如果时段类型是年，返回传入时间所在年的1月1日
            if (tmseg == QmsPointTimeSegmentEnum.YEAR)
                return isplus ? beginPeriodTime.plusYears(count) : beginPeriodTime.minusYears(count);//时段类型为年，年数加减
            if (tmseg == QmsPointTimeSegmentEnum.QUARTER)
                return isplus ? beginPeriodTime.plusMonths(0x3 * count) : beginPeriodTime.minusMonths(0x3 * count);//时段类型为季度，月数加减*3
            if (tmseg == QmsPointTimeSegmentEnum.MONTH)
                return isplus ? beginPeriodTime.plusMonths(count) : beginPeriodTime.minusMonths(count);//时段类型为月，月数加减
            if (tmseg == QmsPointTimeSegmentEnum.TEN_DAYS) {
                //时段类型为旬，需要根据日期判断进行处理
                //如果日期是1、11直接天数+10；如果日期是21则获取获取下个月1号的时间
                for (int i = 0; i < count; i++) {
                    int day = beginPeriodTime.getDayOfMonth();
                    if (isplus == true) {
                        //如果是加时段
                        if (day >= 21)//如果是加时段，并且当前日期是21号，下旬为下个月1号的时间；设置天为1、月份+1
                            beginPeriodTime = beginPeriodTime.withDayOfMonth(0x1).plusMonths(0x1);
                        else//天数+10
                            beginPeriodTime = beginPeriodTime.plusDays(10);
                    } else {
                        //如果是减时段
                        if (day <= 0x1)//如果是减时段，并且当前日期是1号，上旬为上个月21号的时间；设置天为21、月份减1
                            beginPeriodTime = beginPeriodTime.withDayOfMonth(21).minusMonths(0x1);
                        else//天数-10
                            beginPeriodTime = beginPeriodTime.minusDays(10);
                    }
                }
                return beginPeriodTime;
            }
            if (tmseg == QmsPointTimeSegmentEnum.WEEK)
                return isplus ? beginPeriodTime.plusDays(0x7 * count) : beginPeriodTime.minusDays(0x7 * count);//时段类型为周，天数加减*7
            if (tmseg == QmsPointTimeSegmentEnum.DAY)
                return isplus ? beginPeriodTime.plusDays(count) : beginPeriodTime.minusDays(count);//时段类型为天，天数加减
            if (tmseg == QmsPointTimeSegmentEnum.HOUR) {
                //时段类型为小时，如果指定的间隔分钟数有效使用间隔分钟，如无效默认小时+1
                if (intervalMinutes > 0x0)
                    return isplus ? beginPeriodTime.plusMinutes(intervalMinutes * count) : beginPeriodTime.minusMinutes(intervalMinutes * count);
                else
                    return isplus ? beginPeriodTime.plusHours(count) : beginPeriodTime.minusHours(count);//默认小时加减
            }

            if (tmseg == QmsPointTimeSegmentEnum.MINUTE) {
                //时段类型为分钟，如果指定的间隔分钟数有效使用间隔分钟数，如无效默认分钟+1
                if (intervalMinutes > 0x0)
                    return isplus ? beginPeriodTime.plusMinutes(intervalMinutes * count) : beginPeriodTime.minusMinutes(intervalMinutes * count);
                else
                    return isplus ? beginPeriodTime.plusMinutes(count) : beginPeriodTime.minusMinutes(count);//默认分钟加减
            }

            return beginPeriodTime;
        }

        return null;
    }

    /**
     * 获取指定代表时间点的时段长天数
     *
     * @param time  代表时间点
     * @param tmseg 时段类型
     * @return int
     * @author QuCheng Tech
     * @since 2025/9/13
     */
    public static int getSpanDays(LocalDateTime time, QmsPointTimeSegmentEnum tmseg) {
        //日以下时段类型返回跨度天数为0
        if (tmseg.getIndex() <= QmsPointTimeSegmentEnum.HOUR.getIndex())
            return 0x0;

        //日时段类型返回跨度天数为1
        if (tmseg == QmsPointTimeSegmentEnum.DAY)
            return 0x1;

        //周时段类型返回跨度天数为7
        if (tmseg == QmsPointTimeSegmentEnum.WEEK)
            return 0x7;

        if (time != null) {
            //需要根据点号的时段类型及当前数据时标的时间点计算出时间长度；如月点号的总天数可能是28、29、30、31天
            //点号时段类型为旬、月、季度、年时需要根据当前数据时标计算天数；如果是周固定为7天；如果是天以下时段类型不需要乘以天数
            if (tmseg == QmsPointTimeSegmentEnum.TEN_DAYS || tmseg == QmsPointTimeSegmentEnum.MONTH
                    || tmseg == QmsPointTimeSegmentEnum.QUARTER || tmseg == QmsPointTimeSegmentEnum.YEAR) {
                LocalDateTime beginDate = getPeriodTime(time, tmseg);
                if (beginDate != null) {
                    LocalDateTime endDate = getNextPeriodTime(beginDate, tmseg, 0x0);
                    if (endDate != null) {
                        long diffDays = ChronoUnit.DAYS.between(beginDate, endDate);

                        return Math.toIntExact(diffDays);
                    }
                }
            }
        }

        return 0x0;
    }

    /**
     * QMS中的时间格式化输出，根据时段类型进行格式化输出
     *
     * @param time  日期时间
     * @param tmseg 时段类型，可以为null
     * @return java.lang.String
     * @author QuCheng Tech
     * @since 2025/10/30
     */
    public static String format(LocalDateTime time, QmsPointTimeSegmentEnum tmseg) {
        return format(time, tmseg, null);
    }

    /**
     * QMS中的时间格式化输出，根据时段类型或指定的格式化进行输出
     *
     * @param time    日期时间
     * @param tmseg   时段类型，可以为null
     * @param pattern 指定的时间格式化，可以为null，不为空时优先使用指定的格式化
     * @return java.lang.String
     * @author QuCheng Tech
     * @since 2025/10/30
     */
    public static String format(LocalDateTime time, QmsPointTimeSegmentEnum tmseg, String pattern) {
        if (time != null) {
            String formatPattern = null;
            if (StringUtils.isNotBlank(pattern)) {
                formatPattern = pattern;
            } else if (tmseg != null) {
                switch (tmseg) {
                    case MINUTE:
                        formatPattern = "yyyy-MM-dd HH:mm";
                        break;
                    case HOUR:
                        formatPattern = "yyyy-MM-dd HH";
                        break;
                    case DAY:
                        formatPattern = "yyyy-MM-dd";
                        break;
                    case WEEK:
                        formatPattern = "yyyy-MM-dd";
                        break;
                    case TEN_DAYS:
                        formatPattern = "yyyy-MM-dd";
                        break;
                    case MONTH:
                        formatPattern = "yyyy-MM";
                        break;
                    case QUARTER:
                        formatPattern = "yyyy-MM";
                        break;
                    case YEAR:
                        formatPattern = "yyyy";
                        break;
                    default:
                        break;
                }
            }

            //如果有对应的格式化字符串，使用对应的格式化字符串进行转换
            if (StringUtils.isNotBlank(formatPattern))
                return LocalDateTimeUtil.format(time, formatPattern);

            //没有对应的格式化字符串，使用日期时间默认的格式化
            return LocalDateTimeUtil.formatDateTime(time);
        }

        return null;
    }
}
